package com.jiao.table.lua;

/**
 * @Description
 * @Author Vincent.jiao
 * @Date 2022/5/23 10:01
 */
public class CacheExceScriptText {
    /**
     * 0: 执行成功   1:删除失败  2:新增失败   3:未知错误
     * @return
     */
    public static String getUpdCacheData() {
        return getTypeConvert() +
                "\n" +
                "-- 首先删除旧的，然后再新增\n" +
                "local dataJson = cjson.decode(KEYS[1]);\n" +
                "\n" +
                "local addSha = dataJson[\"addSha\"];\n" +
                "local delSha = dataJson[\"delSha\"];\n" +
                "local newCacheCommand = typeConvert(dataJson[\"newCacheCommand\"]);\n" +
                "local oldCacheCommand = typeConvert(dataJson[\"oldCacheCommand\"]);\n" +
                "local resultV = 1;\n" +
                "\n" +
                "local updCache = function()\n" +
                "    local status = 0;\n" +
                "\n" +
                "    -- 如果存在旧数据就删除\n" +
                "    if(oldCacheCommand ~= 'null')\n" +
                "    then\n" +
                getDelCacheCoreScript() +
                "        status = delCache(oldCacheCommand);\n" +
                "    end;\n" +
                "\n" +
                "    if(status == 0)\n" +
                "    then\n" +
                getAddCacheCore() +
                "        status = addCache(newCacheCommand);\n" +
                "\n" +
                "        if(status ~= 0)\n" +
                "        then\n" +
                "            resultV = 2;\n" +
                "        else\n" +
                "            resultV = 0;\n" +
                "        end;\n" +
                "    else\n" +
                "        resultV = 1;\n" +
                "    end;\n" +
                "\n" +
                "end;\n" +
                "\n" +
                "local errorHandler = function(e)\n" +
                "    redis.log(redis.LOG_NOTICE, e);\n" +
                "    redis.log(redis.LOG_NOTICE, \"updErrorInfo >>> script error info: \", cjson.encode(newCacheCommand))\n" +
                "    resultV = 3;\n" +
                "end;\n" +
                "\n" +
                "xpcall(updCache, errorHandler);\n" +
                "\n" +
                "--0: 执行成功   1:删除失败  2:新增失败   3:未知错误\n" +
                "return resultV; ";
    }

    public static String getAddCacheData() {
        return getTypeConvert() +
                "\n" +
               getAddCacheCore() +
                "\n" +
                "return addCache(KEYS[1]); ";
    }

    /**
     * 0: 执行成功   1:执行失败  2:数据已存在  3:回滚失败 4:执行失败但是回滚成功
     * @return
     */
    public static String getAddCacheCore() {
        return " -- 增加缓存\n" +
                "local addCache = function (cacheData)\n" +
                "    local dataJson = cjson.decode(cacheData);\n" +
                "    local data = dataJson[\"data\"];\n" +
                "    local tableNameKey = dataJson[\"tableNameKey\"];\n" +
                "    local primaryKeyValue = dataJson[\"primaryKeyValue\"];\n" +
                "    local bitMapKey = dataJson[\"mateDataBitMapKey\"];\n" +
                "    local indexTable = dataJson[\"indexMap\"];\n" +
                "    local mateDataVersionKey = dataJson[\"mateDataVersionKey\"];\n" +
                "    local mateDataVersionValue = dataJson[\"mateDataVersionValue\"];\n" +
                "    local mateDataIndexMap = dataJson[\"mateDataIndexMap\"];\n" +
                "    local mateDataMaxIdKey = dataJson[\"mateDataMaxIdKey\"];\n" +
                "\n" +
                "    local oldMaxId;\n" +
                "    local oldVersion;\n" +
                "    local resultV = 1;\n" +
                "\n" +
                "    local exectuRedis = function ()\n" +
                "        if(redis.call(\"hexists\", tableNameKey, primaryKeyValue) == 1)\n" +
                "        then\n" +
                "            resultV = 2;\n" +
                "            return;\n" +
                "        end;\n" +
                "\n" +
                "        -- 基本数据\n" +
                "        redis.call(\"hset\", tableNameKey, primaryKeyValue, cjson.encode(data));\n" +
                "        redis.call(\"setbit\", bitMapKey, primaryKeyValue, 1);\n" +
                "\n" +
                "        -- 记录最大的那个id\n" +
                "        oldMaxId = redis.call(\"get\", mateDataMaxIdKey);\n" +
                "\n" +
                "        -- 不存在 或 新增值大于当前 redis 中的值\n" +
                "        if(type(oldMaxId) == 'boolean' or (type(oldMaxId) == 'string' and tonumber(oldMaxId) < primaryKeyValue))\n" +
                "        then\n" +
                "            redis.call(\"set\", mateDataMaxIdKey, primaryKeyValue);\n" +
                "        end;\n" +
                "\n" +
                "        redis.call(\"set\", mateDataVersionKey, mateDataVersionValue);\n" +
                "\n" +
                "        -- 索引数据\n" +
                "        for key, value in pairs(indexTable) do\n" +
                "            redis.call(\"sadd\", key, typeConvert(value));\n" +
                "        end;\n" +
                "\n" +
                "        -- 元数据索引\n" +
                "        for key, value in pairs(mateDataIndexMap) do\n" +
                "            redis.call(\"sadd\", key, typeConvert(value));\n" +
                "        end;\n" +
                "\n" +
                "        resultV = 0;\n" +
                "    end;\n" +
                "\n" +
                "\n" +
                "    --[[\n" +
                "        回滚.\n" +
                "        假如有 10 条命令，执行到第 5 条因为某些原因失败了。\n" +
                "        那么会执行回删，将此次的所有写操作撤回\n" +
                "\n" +
                "        回滚时候可能存在的情况\n" +
                "        1. 回滚出现错误(此条数据为脏数据)，那么此时应该交由客户端去记录做补偿处理\n" +
                "        2. 在集群环境下如果出现极端情况, 如：A机器执行增加成功, 命令传播给 B 机器执行 lua，\n" +
                "            但是 B 机器执行失败。B机器应该回滚，此时为脏数据，但是为无法捕捉到。此类型的错误\n" +
                "            为 Redis 中不可控因素，无法处理。\n" +
                "    --]]\n" +
                "    local rollback = function ()\n" +
                "        -- 清理此次相关所有信息\n" +
                "        redis.call(\"hdel\", tableNameKey, primaryKeyValue);\n" +
                "        redis.call(\"setbit\", bitMapKey, primaryKeyValue, 0);\n" +
                "\n" +
                "        if(oldVersion ~= 'nil')\n" +
                "        then\n" +
                "            redis.call(\"set\", mateDataVersionKey, oldVersion);\n" +
                "        end;\n" +
                "\n" +
                "        if(oldMaxId ~= 'nil')\n" +
                "        then\n" +
                "            redis.call(\"set\", mateDataMaxIdKey, oldMaxId);\n" +
                "        end;\n" +
                "\n" +
                "        for key, value in pairs(indexTable) do\n" +
                "            redis.call(\"srem\", key, typeConvert(value));\n" +
                "        end;\n" +
                "\n" +
                "        for key, value in pairs(mateDataIndexMap) do\n" +
                "            redis.call(\"srem\", key, typeConvert(value));\n" +
                "        end;\n" +
                "    end;\n" +
                "\n" +
                "    local rollbackHandler = function ( err )\n" +
                "        xpcall(\n" +
                "            function()\n" +
                "               redis.log(redis.LOG_NOTICE, commandId, \"addCacheData >>> script error info: \", err)\n" +
                "               redis.log(redis.LOG_NOTICE, commandId, \"addCacheData >>> try rollback, table:\", tableNameKey, \", primaryKey\", primaryKeyValue)\n" +
                "               rollback();\n" +
                "               redis.log(redis.LOG_NOTICE, commandId, \"addCacheData >>> success rollback\")\n" +
                "               resultV = 4;\n" +
                "            end,\n" +
                "            function(rollErr)\n" +
                "                -- 回滚失败写入 redis 日志，手动补偿的解决无效数据\n" +
                "                redis.log(redis.LOG_NOTICE, commandId, \"addCacheData >>> error rollback, data invalid. table:\", tableNameKey, \", primaryKey\", primaryKeyValue)\n" +
                "                redis.log(redis.LOG_NOTICE,rollErr);\n" +
                "                resultV = 3;\n" +
                "            end\n" +
                "        );\n" +
                "    end;\n" +
                "\n" +
                "\n" +
                "    xpcall(exectuRedis, rollbackHandler);\n" +
                "\n" +
                "    --0: 执行成功   1:执行失败  2:数据已存在  3:回滚失败 4:执行失败但是回滚成功\n" +
                "    return resultV;\n" +
                "end;\n";
    }


    public static String getDelCacheData() {
        return getTypeConvert() +
                "\n" +
               getDelCacheCoreScript()  +
                "\n" +
                "return delCache(KEYS[1]); ";
    }

    /**
     * 0: 执行成功   1:执行失败  2:未知错误
     * @return
     */
    public static String getDelCacheCoreScript() {
        return " -- 删除缓存\n" +
                "local delCache = function (cacheData)\n" +
                "    local dataJson = cjson.decode(cacheData);\n" +
                "    local data = dataJson[\"data\"];\n" +
                "    local tableNameKey = dataJson[\"tableNameKey\"];\n" +
                "    local primaryKeyValue = dataJson[\"primaryKeyValue\"];\n" +
                "    local bitMapKey = dataJson[\"mateDataBitMapKey\"];\n" +
                "    local indexTable = dataJson[\"indexMap\"];\n" +
                "    local mateDataVersionKey = dataJson[\"mateDataVersionKey\"];\n" +
                "    local mateDataVersionValue = dataJson[\"mateDataVersionValue\"];\n" +
                "    local mateDataIndexMap = dataJson[\"mateDataIndexMap\"];\n" +
                "    local mateDataMaxIdKey = dataJson[\"mateDataMaxIdKey\"];\n" +
                "\n" +
                "    local isExecReplaceMaxId = false;\n" +
                "    local resultV = 1;\n" +
                "\n" +
                "    local deleteCache = function ()\n" +
                "        redis.call(\"hdel\", tableNameKey, primaryKeyValue);\n" +
                "        redis.call(\"set\", mateDataVersionKey, mateDataVersionValue);\n" +
                "\n" +
                "        local serverMaxId = redis.call(\"get\", mateDataMaxIdKey);\n" +
                "        if(type(serverMaxId) == 'string' and tonumber(serverMaxId) > 0)\n" +
                "        then\n" +
                "            serverMaxId = tonumber(serverMaxId);\n" +
                "            while(true)\n" +
                "            do\n" +
                "                serverMaxId = serverMaxId - 1;\n" +
                "                if(redis.call(\"getbit\", bitMapKey, serverMaxId) == 1)\n" +
                "                then\n" +
                "                   redis.call(\"set\", mateDataMaxIdKey, serverMaxId);\n" +
                "                   break;\n" +
                "                end;\n" +
                "\n" +
                "                if(serverMaxId <= 0)\n" +
                "                then\n" +
                "                    redis.call(\"set\", mateDataMaxIdKey, 0);\n" +
                "                    break;\n" +
                "                end;\n" +
                "            end;\n" +
                "        end;\n" +
                "\n" +
                "        -- TODO 删除有问题，索引与元数据一起删除。目前的问题是，索引删除正确，元数据未删除\n" +
                "        for key, value in pairs(mateDataIndexMap) do\n" +
                "            -- 查看索引是否再有值如果还存在值就不删除\n" +
                "            local tmpVal = typeConvert(value);\n" +
                "            local tmpKey = key .. ':' .. tmpVal;\n" +
                "            tmpKey = string.gsub(tmpKey, \"matedata:\", \"\", 1);\n" +
                "\n" +
                "            if(redis.call(\"scard\", tmpKey) > 1)\n" +
                "            then\n" +
                "                -- 个数大于1说明：索引还存在别的值，不能删除。元数据索引需要保留\n" +
                "                redis.call(\"srem\", tmpKey, primaryKeyValue);\n" +
                "            else\n" +
                "                -- 个数小于等于0说明：索引不存在别的值，可以删除。元数据索引需要删除\n" +
                "                redis.call(\"srem\", key, tmpVal);\n" +
                "                redis.call(\"del\",  tmpKey);\n" +
                "            end;\n" +
                "        end;\n" +
                "\n" +
                "        resultV = 0;\n" +
                "    end;\n" +
                "\n" +
                "    local rollbackHandler = function ( err )\n" +
                "        redis.log(redis.LOG_NOTICE, mateDataVersionValue, \"delCacheData >>>> script error info: \", err)\n" +
                "        resultV = 2;\n" +
                "    end;\n" +
                "\n" +
                "    xpcall(deleteCache, rollbackHandler);\n" +
                "\n" +
                "    --0: 执行成功   1:执行失败  2:未知错误\n" +
                "    return resultV;\n" +
                "end; " ;
    }

    public static String getTypeConvert() {
        return " local typeConvert = function (value)\n" +
                "    if(type(value) == \"nil\")\n" +
                "    then\n" +
                "        return \"null\";\n" +
                "    end;\n" +
                "\n" +
                "    if(type(value) == \"boolean\")\n" +
                "    then\n" +
                "        return tostring(value);\n" +
                "    end;\n" +
                "\n" +
                "    if(type(value) == \"table\")\n" +
                "    then\n" +
                "        return cjson.encode(value);\n" +
                "    end;\n" +
                "\n" +
                "    if(type(value) == \"userdata\")\n" +
                "    then\n" +
                "        return cjson.encode(value);\n" +
                "    end;\n" +
                "\n" +
                "    return value;\n" +
                "end; ";
    }
}
